﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using LinFu.AOP.Interfaces;

namespace BrainTechLLC
{
	public class RestrictPropertyChangeWrapper : AroundMethodBase, IAroundInvoke
	{				
		public override void AfterInvoke(IInvocationContext context, object returnValue)
		{
			// If we skipped executing property set {...} this time, restore 
			// any previous IsInterceptionDisabled value that may have been set
			if (context.ExtraInfo != null)
				RestoreIsInterceptionDisabledFlag(context);
		}

		protected override void Execute(IInvocationContext context, CutpointType when, object returnValue)
		{
			if (!context.TargetMethod.Name.Contains("set_"))
				return;

			object thisObject = context.Target;
			Type t = thisObject.GetType();

			// Method name comes in as set_PropertyName
			// Grab the property name by taking the substring starting at position 4
			string propInfoName = context.TargetMethod.Name.Substring(4);
			
			// Get property info and other details about the property being set
			PropInfo propInfo = t.GetPropInfo(propInfoName);

			// Read any RestrictPropertyChangeAttribute attributes attached to the property
			RestrictPropertyChangeAttribute[] attrs = propInfo.GetAttributes<RestrictPropertyChangeAttribute>();

			if (attrs != null && attrs.Length > 0)
			{
				// Read the old property value and record the new one
				object oldValue = propInfo.GetValue(thisObject);
				object newValue = context.Arguments[0];

				for (int n = 0; n < attrs.Length; n++)
				{
					RestrictPropertyChangeAttribute attr = attrs[n];

					// See if the property change is restricted
					Exception ex = attr.IsRestricted(thisObject, t, propInfo.Name, oldValue, newValue, context);

					if (ex != null)
					{
						// Send notification regarding the restriction, if possible
						ISupportsPropertyChangeRestrictedNotify notify = thisObject as ISupportsPropertyChangeRestrictedNotify;
						
						if (notify != null)
							notify.NotifyPropertyChangeRestricted(context, propInfo.PropertyInfo, oldValue, newValue, ex);

						// Mark that the original method should NOT be executed
						SetInterceptionDisabledFlag(context, true);

						if (attr.ThrowOnException)
							throw ex;
					}
				}
			}
		}

		private void RestoreIsInterceptionDisabledFlag(IInvocationContext context)
		{
			IModifiableType mod = context.Target as IModifiableType;

			// Set the flag back to its original (pre-method-call) value
			mod.IsInterceptionDisabled = Convert.ToBoolean(context.ExtraInfo);

			// Blank out ExtraInfo to mark that we're done using it
			context.ExtraInfo = null;
		}

		private void SetInterceptionDisabledFlag(IInvocationContext context, bool value)
		{
			IModifiableType mod = (context.Target as IModifiableType);

			// Store the old value of the IsInterceptionDisabled flag
			context.ExtraInfo = mod.IsInterceptionDisabled;

			// Mark that we do not want to execute the original method's code
			mod.IsInterceptionDisabled = value;
		}
	}
}

